import numpy as np
from typing import List, Dict, Any, Optional
from sqlalchemy.orm import Session
import logging
from datetime import datetime, timedelta

from app.models.user import User, UserFollow
from app.models.post import Post, PostLike, Tag
from app.models.campsite import Campsite, CampsiteFollow

logger = logging.getLogger(__name__)

class RecommendationService:
    """推荐系统服务"""
    
    def __init__(self):
        """初始化推荐服务"""
        self.logger = logging.getLogger(__name__)
    
    def recommend_posts(self, db: Session, user_id: int, limit: int = 20) -> List[Post]:
        """为用户推荐帖子"""
        self.logger.info(f"为用户 {user_id} 推荐帖子")
        
        try:
            # 获取用户的兴趣特征
            user_interests = self._get_user_interests(db, user_id)
            
            # 获取最近的帖子
            recent_posts = self._get_recent_posts(db, exclude_user_id=user_id)
            
            # 计算相似度得分
            scored_posts = self._score_posts(db, user_interests, recent_posts)
            
            # 排序并返回推荐结果
            recommended_posts = [post for post, _ in sorted(scored_posts, key=lambda x: x[1], reverse=True)[:limit]]
            
            return recommended_posts
            
        except Exception as e:
            self.logger.error(f"推荐帖子出错: {str(e)}")
            # 如果出错，返回热门帖子
            return self._get_popular_posts(db, limit=limit)
    
    def recommend_campsites(self, db: Session, user_id: int, limit: int = 10) -> List[Campsite]:
        """为用户推荐营地"""
        self.logger.info(f"为用户 {user_id} 推荐营地")
        
        try:
            # 获取用户关注的营地
            followed_campsites = db.query(Campsite).join(
                CampsiteFollow, CampsiteFollow.campsite_id == Campsite.id
            ).filter(
                CampsiteFollow.user_id == user_id,
                Campsite.is_active == True,
                Campsite.is_deleted == False
            ).all()
            
            # 如果用户没有关注任何营地，返回热门营地
            if not followed_campsites:
                return self._get_popular_campsites(db, limit=limit)
            
            # 获取所有未关注的活跃营地
            followed_ids = [c.id for c in followed_campsites]
            campsites = db.query(Campsite).filter(
                Campsite.id.notin_(followed_ids),
                Campsite.is_active == True,
                Campsite.is_deleted == False
            ).all()
            
            # 基于用户已关注的营地特征进行推荐
            scored_campsites = self._score_campsites(db, followed_campsites, campsites)
            
            # 排序并返回推荐结果
            recommended_campsites = [campsite for campsite, _ in sorted(scored_campsites, key=lambda x: x[1], reverse=True)[:limit]]
            
            return recommended_campsites
            
        except Exception as e:
            self.logger.error(f"推荐营地出错: {str(e)}")
            # 如果出错，返回热门营地
            return self._get_popular_campsites(db, limit=limit)
    
    def recommend_users(self, db: Session, user_id: int, limit: int = 10) -> List[User]:
        """为用户推荐其他用户"""
        self.logger.info(f"为用户 {user_id} 推荐其他用户")
        
        try:
            # 获取用户已关注的用户
            followed_users = db.query(User).join(
                UserFollow, UserFollow.followed_id == User.id
            ).filter(
                UserFollow.follower_id == user_id,
                User.is_active == True
            ).all()
            
            # 如果用户没有关注任何人，返回活跃用户
            if not followed_users:
                return self._get_active_users(db, limit=limit)
            
            # 获取二度关系用户（用户关注的人关注的人）
            second_degree_users = db.query(User).join(
                UserFollow, UserFollow.followed_id == User.id
            ).filter(
                UserFollow.follower_id.in_([u.id for u in followed_users]),
                User.id != user_id,
                User.is_active == True
            ).all()
            
            # 过滤掉已关注的用户
            followed_ids = [u.id for u in followed_users]
            second_degree_users = [u for u in second_degree_users if u.id not in followed_ids]
            
            # 如果二度关系用户不足，补充活跃用户
            if len(second_degree_users) < limit:
                active_users = self._get_active_users(db, limit=limit*2)
                active_users = [u for u in active_users if u.id not in followed_ids and u.id != user_id]
                
                # 合并列表并去重
                all_users = list(set(second_degree_users + active_users))
                return all_users[:limit]
            
            return second_degree_users[:limit]
            
        except Exception as e:
            self.logger.error(f"推荐用户出错: {str(e)}")
            # 如果出错，返回活跃用户
            return self._get_active_users(db, limit=limit)
    
    def _get_user_interests(self, db: Session, user_id: int) -> Dict[str, Any]:
        """获取用户兴趣特征"""
        # 用户点赞的帖子
        liked_posts = db.query(Post).join(
            PostLike, PostLike.post_id == Post.id
        ).filter(
            PostLike.user_id == user_id,
            Post.is_deleted == False
        ).all()
        
        # 用户关注的营地
        followed_campsites = db.query(Campsite).join(
            CampsiteFollow, CampsiteFollow.campsite_id == Campsite.id
        ).filter(
            CampsiteFollow.user_id == user_id,
            Campsite.is_deleted == False
        ).all()
        
        # 用户自己的帖子
        own_posts = db.query(Post).filter(
            Post.author_id == user_id,
            Post.is_deleted == False
        ).all()
        
        # 收集标签
        tags = set()
        for post in liked_posts + own_posts:
            post_tags = [tag.name for tag in post.tags]
            tags.update(post_tags)
        
        # 收集营地ID
        campsite_ids = set()
        for post in liked_posts + own_posts:
            if post.campsite_id:
                campsite_ids.add(post.campsite_id)
        
        for campsite in followed_campsites:
            campsite_ids.add(campsite.id)
        
        return {
            "tags": list(tags),
            "campsite_ids": list(campsite_ids)
        }
    
    def _get_recent_posts(self, db: Session, days: int = 30, exclude_user_id: Optional[int] = None) -> List[Post]:
        """获取最近的帖子"""
        query = db.query(Post).filter(
            Post.created_at >= datetime.now() - timedelta(days=days),
            Post.is_published == True,
            Post.is_deleted == False
        )
        
        if exclude_user_id:
            query = query.filter(Post.author_id != exclude_user_id)
        
        return query.all()
    
    def _score_posts(self, db: Session, user_interests: Dict[str, Any], posts: List[Post]) -> List[tuple]:
        """为帖子计算得分"""
        scored_posts = []
        
        for post in posts:
            score = 0
            
            # 标签匹配得分
            post_tags = [tag.name for tag in post.tags]
            common_tags = set(post_tags).intersection(set(user_interests["tags"]))
            score += len(common_tags) * 2
            
            # 营地匹配得分
            if post.campsite_id and post.campsite_id in user_interests["campsite_ids"]:
                score += 3
            
            # 根据点赞数增加得分
            score += min(post.like_count, 100) / 20
            
            # 根据评论数增加得分
            score += min(len(post.comments), 50) / 10
            
            # 新鲜度得分（最近发布的帖子得分更高）
            days_old = (datetime.now() - post.created_at).days
            freshness = max(0, 1 - (days_old / 30))  # 最多30天的衰减
            score *= (0.5 + 0.5 * freshness)
            
            scored_posts.append((post, score))
        
        return scored_posts
    
    def _score_campsites(self, db: Session, followed_campsites: List[Campsite], campsites: List[Campsite]) -> List[tuple]:
        """为营地计算得分"""
        scored_campsites = []
        
        # 提取已关注营地的特征
        followed_locations = [(c.latitude, c.longitude) for c in followed_campsites if c.latitude and c.longitude]
        
        for campsite in campsites:
            score = 0
            
            # 基于地理位置的得分
            if campsite.latitude and campsite.longitude and followed_locations:
                min_distance = min(
                    self._haversine_distance(
                        campsite.latitude, campsite.longitude, 
                        lat, lon
                    ) for lat, lon in followed_locations
                )
                # 距离越近得分越高，最大考虑100公里
                proximity_score = max(0, 1 - (min_distance / 100))
                score += proximity_score * 3
            
            # 基于帖子数量的得分
            post_count = len(campsite.posts)
            score += min(post_count, 50) / 10
            
            # 基于关注数的得分
            follower_count = len(campsite.followers)
            score += min(follower_count, 100) / 20
            
            scored_campsites.append((campsite, score))
        
        return scored_campsites
    
    def _get_popular_posts(self, db: Session, days: int = 30, limit: int = 20) -> List[Post]:
        """获取热门帖子"""
        recent_date = datetime.now() - timedelta(days=days)
        
        return db.query(Post).filter(
            Post.created_at >= recent_date,
            Post.is_published == True,
            Post.is_deleted == False
        ).order_by(Post.like_count.desc()).limit(limit).all()
    
    def _get_popular_campsites(self, db: Session, limit: int = 10) -> List[Campsite]:
        """获取热门营地"""
        # 此处简化实现，按关注者数量排序
        return db.query(Campsite).filter(
            Campsite.is_active == True,
            Campsite.is_deleted == False
        ).order_by(Campsite.followers.count().desc()).limit(limit).all()
    
    def _get_active_users(self, db: Session, days: int = 30, limit: int = 10) -> List[User]:
        """获取活跃用户"""
        recent_date = datetime.now() - timedelta(days=days)
        
        # 按发帖数排序
        return db.query(User).filter(
            User.is_active == True,
            User.posts.any(Post.created_at >= recent_date)
        ).order_by(User.posts.count().desc()).limit(limit).all()
    
    def _haversine_distance(self, lat1: float, lon1: float, lat2: float, lon2: float) -> float:
        """计算两点之间的Haversine距离（公里）"""
        # 将经纬度转换为弧度
        lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
        
        # Haversine公式
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
        c = 2 * np.arcsin(np.sqrt(a))
        r = 6371  # 地球半径（公里）
        
        return c * r


# 创建推荐服务单例
recommendation_service = RecommendationService() 